package dao

import (
	"fmt"
	"go-gateway-learn/public"
	load_balance "go-gateway-learn/reverseproxy/loadbalance"
	"net"
	"net/http"
	"strings"
	"sync"
	"time"

	"github.com/gin-gonic/gin"
	"gorm.io/gorm"
)

type LoadBalance struct {
	Id                     int64  `json:"id" gorm:"primary_key" description:"自增主键"`
	ServiceID              int64  `json:"service_id" gorm:"column:service_id" description:"服务id"`
	CheckMethod            int    `json:"check_method" gorm:"column:check_method" description:"检查方法 tcpchk=检测端口是否握手成功	"`
	CheckTimeout           int    `json:"check_timeout" gorm:"column:check_timeout" description:"check超时时间	"`
	CheckInterval          int    `json:"check_interval" gorm:"column:check_interval" description:"检查间隔, 单位s		"`
	RoundType              int    `json:"round_type" gorm:"column:round_type" description:"轮询方式 round/weight_round/random/ip_hash"`
	IPList                 string `json:"ip_list" gorm:"column:ip_list" description:"ip列表"`
	WeightList             string `json:"weight_list" gorm:"column:weight_list" description:"权重列表"`
	ForbidList             string `json:"forbid_list" gorm:"column:forbid_list" description:"禁用ip列表"`
	UpstreamConnectTimeout int    `json:"upstream_connect_timeout" gorm:"column:upstream_connect_timeout" description:"下游建立连接超时, 单位s"`
	UpstreamHeaderTimeout  int    `json:"upstream_header_timeout" gorm:"column:upstream_header_timeout" description:"下游获取header超时, 单位s	"`
	UpstreamIdleTimeout    int    `json:"upstream_idle_timeout" gorm:"column:upstream_idle_timeout" description:"下游链接最大空闲时间, 单位s	"`
	UpstreamMaxIdle        int    `json:"upstream_max_idle" gorm:"column:upstream_max_idle" description:"下游最大空闲链接数"`
}

func (t *LoadBalance) TableName() string {
	return "gateway_service_load_balance"
}

func (t *LoadBalance) Find(c *gin.Context, db *gorm.DB, search *LoadBalance) (*LoadBalance, error) {
	out := &LoadBalance{}
	err := db.WithContext(c).Where(search).Find(out).Error
	if err != nil {
		return nil, err
	}
	return out, nil
}

func (t *LoadBalance) Save(c *gin.Context, db *gorm.DB) error {
	return db.WithContext(c).Save(t).Error
}

func (t *LoadBalance) GetIPListByModel() []string {
	return strings.Split(t.IPList, ",")
}

func (t *LoadBalance) GetWeightListByModel() []string {
	return strings.Split(t.WeightList, ",")
}

type LoadBalancer struct{
	LoadBanlanceMap map[string]*LoadBalancerItem
	LoadBanlanceSlice []*LoadBalancerItem
	Locker sync.RWMutex
}

type LoadBalancerItem struct {
	LoadBanlance load_balance.LoadBalance
	ServiceName  string
}
var LoadBalancerHandler *LoadBalancer

func init() {
	LoadBalancerHandler = NewLoadBanlancer()
}

func NewLoadBanlancer() *LoadBalancer{
	return &LoadBalancer{
		LoadBanlanceMap: map[string]*LoadBalancerItem{},
		LoadBanlanceSlice: []*LoadBalancerItem{},
		Locker: sync.RWMutex{},
	}
}

func (lbr *LoadBalancer) GetLoadBalancer(service *ServiceDetail) (load_balance.LoadBalance, error) {
	for _, loadBanlanceItem := range lbr.LoadBanlanceSlice{
		if loadBanlanceItem.ServiceName == service.Info.ServiceName{
			return loadBanlanceItem.LoadBanlance, nil
		}
	}
	schema := "http://"
	if service.HTTPRule.NeedHttps == 1 {
		schema = "https://"
	}
	if service.Info.LoadType==public.LoadTypeTCP || service.Info.LoadType==public.LoadTypeGRPC{
		schema = ""
	}
	ipList := service.LoadBalance.GetIPListByModel()
	weightList := service.LoadBalance.GetWeightListByModel() 
	ipConf := map[string]string{}
	for ipIndex, ipItem := range ipList {
		ipConf[ipItem] = weightList[ipIndex]
	}
	//fmt.Println("ipConf", ipConf)
	mConf, err := load_balance.NewLoadBalanceCheckConf(fmt.Sprintf("%s%s", schema, "%s"), ipConf)
	if err != nil {
		return nil, err
	}
	lb := load_balance.LoadBanlanceFactorWithConf(load_balance.LbType(service.LoadBalance.RoundType), mConf)
	lbItem := &LoadBalancerItem{
		LoadBanlance: lb,
		ServiceName:  service.Info.ServiceName,
	}
	lbr.LoadBanlanceSlice = append(lbr.LoadBanlanceSlice, lbItem)

	lbr.Locker.Lock()
	defer lbr.Locker.Unlock()
	lbr.LoadBanlanceMap[service.Info.ServiceName] = lbItem
	return lb, nil
}

var TransportorHandler *Transportor

type Transportor struct{
	TransportMap map[string]*TransportItem
	TransportSlice []*TransportItem
	Locker sync.RWMutex
}

type TransportItem struct{
	Trans *http.Transport
	ServiceName string
}

func NewTransportor() *Transportor{
	return &Transportor{
		TransportMap: map[string]*TransportItem{},
		TransportSlice: []*TransportItem{},
		Locker: sync.RWMutex{},
	}
}

func init(){
	TransportorHandler = NewTransportor()
}

func(t *Transportor) GetTrans(s *ServiceDetail) (*http.Transport,error){
	for _, transItem := range t.TransportSlice{
		if transItem.ServiceName == s.Info.ServiceName{
			return transItem.Trans, nil
		}
	}
	if s.LoadBalance.UpstreamConnectTimeout==0{
		s.LoadBalance.UpstreamConnectTimeout = 30
	}
	if s.LoadBalance.UpstreamMaxIdle==0{
		s.LoadBalance.UpstreamMaxIdle = 100
	}
	if s.LoadBalance.UpstreamIdleTimeout==0{
		s.LoadBalance.UpstreamIdleTimeout = 90
	}
	if s.LoadBalance.UpstreamHeaderTimeout==0{
		s.LoadBalance.UpstreamHeaderTimeout = 30
	}
	trans := &http.Transport{
		Proxy: http.ProxyFromEnvironment,
		DialContext: (&net.Dialer{
			Timeout: time.Duration(s.LoadBalance.UpstreamConnectTimeout)*time.Second,
			KeepAlive: 30*time.Second,
			DualStack: true,
		}).DialContext,
		ForceAttemptHTTP2: true,
		MaxIdleConns: s.LoadBalance.UpstreamMaxIdle,
		IdleConnTimeout: time.Duration(s.LoadBalance.UpstreamIdleTimeout)*time.Second,
		TLSHandshakeTimeout: 10*time.Second,
		ResponseHeaderTimeout: time.Duration(s.LoadBalance.UpstreamHeaderTimeout)*time.Second,
	}
	transItem := &TransportItem{
		Trans: trans,
		ServiceName: s.Info.ServiceName,
	}
	t.TransportSlice = append(t.TransportSlice,transItem)
	t.Locker.Lock()
	defer t.Locker.Unlock()
	t.TransportMap[s.Info.ServiceName] = transItem
	return trans, nil
}